/*!
 * \file StackTracer.cpp
 * \project WonderTrader
 *
 * \author Wesley
 * \date 2021/04/13
 * 
 * \brief 堆栈跟踪器实现文件
 * 
 * \details 本文件实现了跨平台的堆栈跟踪功能，用于在程序异常时获取调用堆栈信息。
 *          不同平台的实现方式：
 *          
 *          Windows平台（MSVC）：
 *          - 使用StackWalker类进行堆栈遍历
 *          - 依赖psapi.lib和dbghelp.lib库
 *          - 支持符号解析和源码定位
 *          
 *          Windows平台（GCC）：
 *          - 由于GCC在Windows下的限制，暂不支持堆栈跟踪
 *          
 *          Linux/Unix平台：
 *          - 使用backtrace()函数获取堆栈地址
 *          - 使用backtrace_symbols()解析符号信息
 *          - 使用__cxa_demangle()进行C++符号反混淆
 */

#include "StackTracer.h"

#ifdef _WIN32
#	ifdef _MSC_VER
#include "StackWalker.h"
#pragma comment(lib, "psapi.lib")    // 进程和内存信息API库
#pragma comment(lib, "dbghelp.lib")  // 调试帮助库

/*!
 * \brief Windows平台堆栈跟踪实现（MSVC版本）
 * 
 * \details 使用StackWalker类获取和输出堆栈跟踪信息。
 *          StackWalker是一个专门用于Windows平台的堆栈遍历工具，
 *          能够提供详细的函数调用信息，包括函数名、模块名、地址等。
 * 
 * \param cb 日志回调函数，用于输出堆栈信息
 */
void print_stack_trace(TracerLogCallback cb)
{
	cb("Uncaught exception");  // 输出异常提示信息
	StackWalker sw(cb);        // 创建堆栈遍历器
	sw.ShowCallstack();        // 显示调用堆栈
}
#	else //_GCC
/*!
 * \brief Windows平台堆栈跟踪实现（GCC版本）
 * 
 * \details 由于GCC在Windows平台下缺乏必要的调试信息支持，
 *          暂时无法提供堆栈跟踪功能，只输出提示信息。
 * 
 * \param cb 日志回调函数，用于输出提示信息
 */
void print_stack_trace(TracerLogCallback cb) {
	cb("Cannot print stack trace due to being build on windows with GCC");
}
#	endif
#else
#include <cerrno>
#include <cstdlib>     // 标准库函数，包含free()
#include <execinfo.h>  // Linux堆栈跟踪函数
#include <cxxabi.h>    // C++ ABI，用于符号反混淆

/*!
 * \brief Linux/Unix平台堆栈跟踪实现
 * 
 * \details 使用Linux标准库函数实现堆栈跟踪功能。实现步骤：
 *          1. 使用backtrace()获取堆栈地址数组
 *          2. 使用backtrace_symbols()将地址转换为符号信息
 *          3. 解析符号信息，提取函数名和偏移量
 *          4. 使用__cxa_demangle()对C++符号进行反混淆
 *          5. 格式化输出堆栈信息
 * 
 * \param cb 日志回调函数，用于输出堆栈信息
 */
void print_stack_trace(TracerLogCallback cb) {
	unsigned int max_frames = 127;  // 最大堆栈帧数
	
	// 存储堆栈地址的数组
	void *addrlist[max_frames + 1];

	// 获取当前堆栈地址
	unsigned int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void *));

	if (addrlen == 0) {
		cb("no trace fetched");  // 无法获取堆栈信息
		return;
	}

	// 将地址解析为包含"文件名(函数名+地址)"格式的字符串
	// 实际格式为：## 程序地址 函数名 + 偏移量
	// 这个数组需要用free()释放
	char **symbollist = backtrace_symbols(addrlist, addrlen);

	// 遍历返回的符号行，跳过前几个，它们是当前函数的地址
	for (unsigned int i = 4; i < addrlen; i++) {
		char *begin_name = nullptr;    // 函数名开始位置
		char *begin_offset = nullptr;  // 偏移量开始位置
		char *end_offset = nullptr;    // 偏移量结束位置

		// 解析符号格式：./module(function+0x15c) [0x8048a6d]
		for (char *p = symbollist[i]; *p; ++p) {
			if (*p == '(')
				begin_name = p;
			else if (*p == '+')
				begin_offset = p;
			else if (*p == ')' && (begin_offset || begin_name))
				end_offset = p;
		}

		// 如果成功解析出函数名和偏移量
		if (begin_name && end_offset && (begin_name > end_offset)) {
			*begin_name++ = '\0';  // 分割字符串
			*end_offset++ = '\0';
			if (begin_offset)
				*begin_offset++ = '\0';

			// 混淆的函数名现在在[begin_name, begin_offset)中
			// 调用者偏移量在[begin_offset, end_offset)中
			// 现在应用__cxa_demangle()进行反混淆

			int status = 0;
			size_t funcnamesize = 8192;
			char funcname[8192];
			char *ret = abi::__cxa_demangle(begin_name, funcname, &funcnamesize, &status);
			cb(ret);  // 输出反混淆后的函数名
			
			// 格式化输出完整的堆栈信息
			char buf[256] = { 0 };
			sprintf(buf, "%30s ( %40s  + %6s) %s", 
				symbollist[i], 
				status == 0 ? ret : begin_name,  // 使用反混淆后的名称或原始名称
				begin_offset ? begin_offset : "", 
				end_offset);
			cb(buf);
		} else {
			// 无法解析该行，直接输出整行内容
			cb(symbollist[i]);
		}
	}
	free(symbollist);  // 释放符号列表内存
}
#endif // _WIN32
